package cn.dansj.common.utils.json;

import cn.dansj.common.utils.lang.Nullable;
import cn.dansj.common.utils.transfer.ArrayUtils;
import cn.dansj.common.utils.transfer.DateTime;
import com.fasterxml.jackson.core.type.TypeReference;

import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class JSONObject extends LinkedHashMap<String, Object> {
    public JSONObject() {
    }

    public JSONObject(int initialCapacity) {
        super(initialCapacity);
    }

    public JSONObject(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
    }

    public JSONObject(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    public JSONObject(Map map) {
        super(map);
    }

    public JSONObject(String json) {
        super(JSONUtils.toObj(JSONUtils.isJSON(json) ? json : "{}", new TypeReference<Map<String, Object>>() {
        }));
    }

    public Object get(Object key) {
        if (key instanceof Number || key instanceof Character || key instanceof Boolean || key instanceof UUID) {
            Object value = super.get(key.toString());
            if (value != null) {
                return value;
            }
        }

        return super.get(key);
    }

    public String getString(String key) {
        Object value = super.get(key);
        if (value == null) {
            return "";
        } else if (value instanceof String) {
            return (String) value;
        } else if (value instanceof Date) {
            return DateTime.getInstance((Date) value).toString();
        } else {
            return !(value instanceof Boolean) && !(value instanceof Character) && !(value instanceof Number) && !(value instanceof UUID) && !(value instanceof Enum) && !(value instanceof TemporalAccessor) ? JSONUtils.toJSON(value) : value.toString();
        }
    }

    @Nullable
    public Double getDouble(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Double) {
            return (Double) value;
        } else if (value instanceof Number) {
            return ((Number) value).doubleValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Double.parseDouble(str) : null;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to Double");
        }
    }

    public double getDoubleValue(String key) {
        Object value = super.get(key);
        if (value == null) {
            return 0.0F;
        } else if (value instanceof Number) {
            return ((Number) value).doubleValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Double.parseDouble(str) : (double) 0.0F;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to double value");
        }
    }

    @Nullable
    public Float getFloat(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Float) {
            return (Float) value;
        } else if (value instanceof Number) {
            return ((Number) value).floatValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Float.parseFloat(str) : null;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to Float");
        }
    }

    public float getFloatValue(String key) {
        Object value = super.get(key);
        if (value == null) {
            return 0.0F;
        } else if (value instanceof Number) {
            return ((Number) value).floatValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Float.parseFloat(str) : 0.0F;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to float value");
        }
    }

    @Nullable
    public Long getLong(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Long) {
            return (Long) value;
        } else if (value instanceof Number) {
            return ((Number) value).longValue();
        } else if (value instanceof String) {
            String str = (String) value;
            if (!str.isEmpty() && !"null".equalsIgnoreCase(str)) {
                return str.indexOf(46) != -1 ? (long) Double.parseDouble(str) : Long.parseLong(str);
            } else {
                return null;
            }
        } else if (value instanceof Boolean) {
            return (Boolean) value ? 1L : 0L;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to Long");
        }
    }

    public long getLongValue(String key) {
        Object value = super.get(key);
        if (value == null) {
            return 0L;
        } else if (value instanceof Number) {
            return ((Number) value).longValue();
        } else if (value instanceof String) {
            String str = (String) value;
            if (!str.isEmpty() && !"null".equalsIgnoreCase(str)) {
                return str.indexOf(46) != -1 ? (long) Double.parseDouble(str) : Long.parseLong(str);
            } else {
                return 0L;
            }
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to long value");
        }
    }

    public long getLongValue(String key, long defaultValue) {
        Object value = super.get(key);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Number) {
            return ((Number) value).longValue();
        } else if (value instanceof String) {
            String str = (String) value;
            if (!str.isEmpty() && !"null".equalsIgnoreCase(str)) {
                return str.indexOf(46) != -1 ? (long) Double.parseDouble(str) : Long.parseLong(str);
            } else {
                return defaultValue;
            }
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to long value");
        }
    }

    @Nullable
    public Integer getInteger(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Integer) {
            return (Integer) value;
        } else if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof String) {
            String str = (String) value;
            if (!str.isEmpty() && !"null".equalsIgnoreCase(str)) {
                return str.indexOf(46) != -1 ? (int) Double.parseDouble(str) : Integer.parseInt(str);
            } else {
                return null;
            }
        } else if (value instanceof Boolean) {
            return (Boolean) value ? 1 : 0;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to Integer");
        }
    }

    public int getIntValue(String key) {
        Object value = super.get(key);
        if (value == null) {
            return 0;
        } else if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof String) {
            String str = (String) value;
            if (!str.isEmpty() && !"null".equalsIgnoreCase(str)) {
                return str.indexOf(46) != -1 ? (int) Double.parseDouble(str) : Integer.parseInt(str);
            } else {
                return 0;
            }
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to int value");
        }
    }

    public int getIntValue(String key, int defaultValue) {
        Object value = super.get(key);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof String) {
            String str = (String) value;
            if (!str.isEmpty() && !"null".equalsIgnoreCase(str)) {
                return str.indexOf(46) != -1 ? (int) Double.parseDouble(str) : Integer.parseInt(str);
            } else {
                return defaultValue;
            }
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to int value");
        }
    }

    @Nullable
    public Short getShort(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Short) {
            return (Short) value;
        } else if (value instanceof Number) {
            return ((Number) value).shortValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Short.parseShort(str) : null;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to Short");
        }
    }

    public short getShortValue(String key) {
        Object value = super.get(key);
        if (value == null) {
            return 0;
        } else if (value instanceof Number) {
            return ((Number) value).shortValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Short.parseShort(str) : 0;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to short value");
        }
    }

    @Nullable
    public Byte getByte(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            return ((Number) value).byteValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Byte.parseByte(str) : null;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to Byte");
        }
    }

    public byte getByteValue(String key) {
        Object value = super.get(key);
        if (value == null) {
            return 0;
        } else if (value instanceof Number) {
            return ((Number) value).byteValue();
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? Byte.parseByte(str) : 0;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to byte value");
        }
    }

    @Nullable
    public byte[] getBytes(String key) {
        Object value = this.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof byte[]) {
            return (byte[]) value;
        } else if (value instanceof String) {
            return Base64.getDecoder().decode((String) value);
        } else {
            throw new ClassCastException("can not cast to byte[], value : " + value);
        }
    }

    @Nullable
    public Boolean getBoolean(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof Number) {
            return ((Number) value).intValue() == 1;
        } else if (!(value instanceof String)) {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to Boolean");
        } else {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? "true".equalsIgnoreCase(str) || "1".equals(str) : null;
        }
    }

    public boolean getBooleanValue(String key) {
        Object value = super.get(key);
        if (value == null) {
            return false;
        } else if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof Number) {
            return ((Number) value).intValue() == 1;
        } else if (!(value instanceof String)) {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to boolean value");
        } else {
            String str = (String) value;
            return "true".equalsIgnoreCase(str) || "1".equals(str);
        }
    }

    public boolean getBooleanValue(String key, boolean defaultValue) {
        Object value = super.get(key);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof Number) {
            return ((Number) value).intValue() == 1;
        } else if (!(value instanceof String)) {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to boolean value");
        } else {
            String str = (String) value;
            return "true".equalsIgnoreCase(str) || "1".equals(str);
        }
    }

    @Nullable
    public BigInteger getBigInteger(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof BigInteger) {
            return (BigInteger) value;
        } else if (value instanceof Number) {
            if (value instanceof BigDecimal) {
                return ((BigDecimal) value).toBigInteger();
            } else {
                long longValue = ((Number) value).longValue();
                return BigInteger.valueOf(longValue);
            }
        } else if (value instanceof String) {
            String str = (String) value;
            return !str.isEmpty() && !"null".equalsIgnoreCase(str) ? new BigInteger(str) : null;
        } else if (value instanceof Boolean) {
            return (Boolean) value ? BigInteger.ONE : BigInteger.ZERO;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to BigInteger");
        }
    }

    @Nullable
    public BigDecimal getBigDecimal(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            if (value instanceof BigDecimal) {
                return (BigDecimal) value;
            } else if (value instanceof BigInteger) {
                return new BigDecimal((BigInteger) value);
            } else if (value instanceof Float) {
                return BigDecimal.valueOf((Float) value);
            } else if (value instanceof Double) {
                return BigDecimal.valueOf((Double) value);
            } else {
                long longValue = ((Number) value).longValue();
                return BigDecimal.valueOf(longValue);
            }
        } else if (value instanceof String) {
            return new BigDecimal((String) value);
        } else if (value instanceof Boolean) {
            return (Boolean) value ? BigDecimal.ONE : BigDecimal.ZERO;
        } else {
            throw new ClassCastException("Can not cast '" + value.getClass() + "' to BigDecimal");
        }
    }

    @Nullable
    public Date getDate(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Date) {
            return (Date) value;
        } else if (value instanceof String) {
            return DateTime.getInstance((String) value).toDate();
        } else if (value instanceof Number) {
            long millis = ((Number) value).longValue();
            return new Date(millis);
        } else {
            return DateTime.toDate(value);
        }
    }

    public Date getDate(String key, Date defaultValue) {
        Date date = this.getDate(key);
        if (date == null) {
            date = defaultValue;
        }

        return date;
    }

    @Nullable
    public Instant getInstant(String key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        } else if (value instanceof Instant) {
            return (Instant) value;
        } else if (value instanceof Number) {
            long millis = ((Number) value).longValue();
            return millis == 0L ? null : Instant.ofEpochMilli(millis);
        } else {
            return DateTime.toInstant(value);
        }
    }

    @Override
    public String toString() {
        return JSONUtils.toJSON(this);
    }

    public JSONObject getJSONObject(String key) {
        Object value = super.get(key);

        if (value == null) {
            return new JSONObject();
        }

        if (value instanceof JSONObject) {
            return (JSONObject) value;
        }

        if (value instanceof String) {
            String str = (String) value;
            if (str.isEmpty() || "null".equalsIgnoreCase(str)) {
                return new JSONObject();
            }
            return JSONUtils.toObj(str, JSONObject.class);
        }

        if (value instanceof Map<?, ?>) {
            JSONObject object = new JSONObject((Map<?, ?>) value);
            put(key, object);
            return object;
        }

        return JSONUtils.toObj(value, JSONObject.class);
    }

    public JSONObject putAll(JSONObject m) {
        super.putAll(m);
        return this;
    }

    public JSONObject put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    /**
     * 在原有的map上移除指定规则的key
     *
     * @param filter 移除规则  jsonObject.removeIf((e)->e.startsWith("aaa"))
     */
    public boolean removeIf(Predicate<? super Map.Entry<String, Object>> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<Map.Entry<String, Object>> each = entrySet().iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

    public Map<String, Object> getIf(Predicate<? super Map.Entry<String, Object>> filter) {
        Objects.requireNonNull(filter);

        final Iterator<Map.Entry<String, Object>> each = entrySet().iterator();
        Map<String, Object> result = new HashMap<>();
        while (each.hasNext()) {
            Map.Entry<String, Object> next = each.next();
            if (filter.test(next)) {
                result.put(next.getKey(), next.getValue());
            }
        }
        return result;
    }

    /**
     * 筛选指定规则的key重新生成一个新的JSONObject
     *
     * @param filter 筛选规则  jsonObject.filter((e)->e.startsWith("aaa"))
     */
    public JSONObject filter(Predicate<? super Map.Entry<String, Object>> filter) {
        return filter(filter, (e) -> e);
    }

    /**
     * 筛选指定规则的key,并根据function重新调整key的名称，然后重新生成一个新的JSONObject
     *
     * @param filter 筛选规则  jsonObject.filter((e)->e.startsWith("aaa"),(e)->e.replace("aaa",""))
     */
    public JSONObject filter(Predicate<? super Map.Entry<String, Object>> filter, Function<String, String> function) {
        Objects.requireNonNull(filter);
        final JSONObject map = new JSONObject();
        for (Map.Entry<String, Object> next : entrySet()) {
            if (filter.test(next)) {
                map.put(function.apply(next.getKey()), next.getValue());
            }
        }
        return map;
    }

    public List<Object> getArrayList(String key) {
        Object value = super.get(key);

        if (value == null) {
            return new ArrayList<>();
        }

        if (value instanceof List) {
            return (List) value;
        }

        if (value instanceof JSONObject) {
            return ArrayUtils.asList(value);
        }

        if (value instanceof String) {
            String stringValue = (String) value;
            if (stringValue.isEmpty() || "null".equalsIgnoreCase(stringValue)) {
                return null;
            }

            if (stringValue.charAt(0) != '[') {
                return ArrayUtils.asList(stringValue);
            }

            return JSONUtils.toObj(stringValue, new TypeReference<List<Object>>() {
            });
        }

        if (value instanceof Collection<?>) {
            List<Object> array = new ArrayList<>((Collection<?>) value);
            put(key, array);
            return array;
        }

        if (value instanceof Object[]) {
            return ArrayUtils.asList((Object[]) value);
        }

        Class<?> valueClass = value.getClass();
        if (valueClass.isArray()) {
            int length = Array.getLength(value);
            List<Object> jsonArray = new ArrayList<>(length);
            for (int i = 0; i < length; i++) {
                Object item = Array.get(value, i);
                jsonArray.add(item);
            }
            return jsonArray;
        }

        return new ArrayList<>();
    }

    public <T> List<T> getArrayList(String key, Class<T> clazz) {
        return getArrayList(key).stream().map(item -> JSONUtils.toObj(item, clazz, Feature.FILED_BASE, Feature.SMART_MATCH)).collect(Collectors.toList());
    }

    public <T> List<T> getArrayList(String key, TypeReference<T> typeReference) {
        return getArrayList(key).stream().map(item -> JSONUtils.toObj(item, typeReference, Feature.FILED_BASE, Feature.SMART_MATCH)).collect(Collectors.toList());
    }

    public <T> List<T> getArrayList(String key, Type type) {
        List<?> arrayList = getArrayList(key);
        List<T> result = new ArrayList<>(arrayList.size());
        arrayList.forEach(item -> result.add(JSONUtils.toObj(item, type, Feature.FILED_BASE, Feature.SMART_MATCH)));
        return result;
    }

    public <T> T get(String key, Class<T> clazz) {
        return JSONUtils.toObj(get(key), clazz);
    }

    public <T> T get(String key, Type type) {
        return JSONUtils.toObj(get(key), type);
    }

    public <T> T get(String key, TypeReference<T> typeReference) {
        return JSONUtils.toObj(get(key), typeReference);
    }

    public static JSONObject of(Object... objects) {
        JSONObject jsonObject = new JSONObject();
        int size = objects.length;

        if ((size & 1) == 1) {
            // 如果number不为偶数, 长度减1, 对应的值默认为null
            size -= 1;
        }

        int keyIndex = 0;
        int valueIndex = 1;
        while (size >= valueIndex) {
            jsonObject.put(String.valueOf(objects[keyIndex]), objects[valueIndex]);
            keyIndex += 2;
            valueIndex += 2;
        }
        return jsonObject;
    }

    public JSONNode at(String path) {
        return JSONUtils.at(this, path);
    }

    public JSONObject putIfExist(String key, Object value) {
        if (this.containsKey(key)) put(key, value);
        return this;
    }

    public JSONObject putIfExist(Map<String, Object> map) {
        map.forEach((key, value) -> {
            if (this.containsKey(key)) put(key, value);
        });
        return this;
    }
}
